/*
 * http.hpp
 *
 *  Created on: Dec 5, 2010
 *      Author: daniel
 */

#ifndef HTTP_CON_HPP_
#define HTTP_CON_HPP_

#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <string>
#include <cstring>
#include <list>
#include <boost/format.hpp>
#include <doeplus/webapp/connection.hpp>
#include <doeplus/webapp/server.hpp>

#define HTTP_READ_BUFFER_SIZE 4096
#define HTTP_RESPONSE_BUFFER_SIZE 1024
#define HTTP_RESPONSE_MAX_SENDING 6


namespace doeplus{ namespace webapp{
	class server;
		namespace protocol {

class http_connection : public doeplus::webapp::connection {

protected:

	class buffer;
	typedef boost::shared_ptr<buffer> buffer_ptr;
	typedef std::list<buffer_ptr> buffer_list;
	class buffer{
	public:
		char _buffer[HTTP_RESPONSE_BUFFER_SIZE+1];
		std::size_t _size;
		buffer() : _size(0) { }
	};

	buffer_list response_buffer_sending;
	buffer_list response_buffer_pending;
	buffer_ptr current_response_buffer;

private:
	boost::asio::ip::tcp::socket _socket;

	bool close_response;

	int http_version_major;
	int http_version_minor;

	enum parsing_state{
		method_start,
		method,
		uri,
		http_major,
		http_dot,
		http_minor,
		new_line,
		header,
		request_end,
		request_post
	} _parsing_step;

	enum parsing_doing{
		doing_nothing,
		doing_done,
		doing_waitforchars,
		doing_waitfordigit
	} _parsing_doing;

	std::string _expected_string;
	std::size_t _expected_string_count;

	const char *wait_for_chars;
	std::size_t wait_for_chars_size;
	std::size_t wait_for_chars_matched;
	std::size_t wait_for_chars_max_limit;
	int wait_for_digit;

	std::string current_string;
//	std::string current_header_key;
//	std::string current_header_value;

	enum parser_returncodes{
		request_done,
		request_unfinished,
		request_bad
	};

	char _read_buffer[HTTP_READ_BUFFER_SIZE+1];

public:
//	typedef boost::shared_ptr<http_connection> ptr;
	doeplus::webapp::server* _server;

	boost::asio::ip::tcp::socket& socket(){
		return this->_socket;
	}

	http_connection(boost::asio::io_service &io_service,string_map &default_headers):
		connection(default_headers),
		current_response_buffer(new buffer()),
		_socket(io_service),
		close_response(false),
		http_version_major(0),
		http_version_minor(0),
		_parsing_step(method_start),
		_parsing_doing(doing_nothing)
	{
	}

	~http_connection(){
		boost::system::error_code ec;
		this->_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
	}

private:

	static int _str_find(const char *chr,char *buffer,int max_size){
		for(int pos(0);pos<max_size;++pos){
			if(buffer[pos]==*chr){
				return pos;
			}
		}
		return -1;
	}

	void _set_waiting_for_chars(const char *text,std::size_t size,int max_limit){
		this->wait_for_chars	= text;
		this->wait_for_chars_size	= size;
		this->wait_for_chars_matched	= 0;
		this->_parsing_doing	= doing_waitforchars;
		this->wait_for_chars_max_limit	= max_limit;
	}

	void handle_read(const boost::system::error_code& error,std::size_t bytes_transferred){

		std::size_t pos(0);
		parser_returncodes returncode(request_unfinished);

		do{
			if(this->_parsing_doing!=doing_nothing){
				std::size_t hit(pos);
				switch(this->_parsing_doing){
					case doing_waitforchars:
						if(this->wait_for_chars_matched==0){
							for(;hit<bytes_transferred;++hit){
								if(this->_read_buffer[hit]==this->wait_for_chars[0]){
									++this->wait_for_chars_matched;
									++hit;
									break;
								}
							}
						}
						if(this->wait_for_chars_matched!=0){
							while(this->_read_buffer[hit]==this->wait_for_chars[this->wait_for_chars_matched] && this->wait_for_chars_matched<=this->wait_for_chars_size && hit<bytes_transferred){
								++hit;
								++this->wait_for_chars_matched;
							}
						}

						if(this->wait_for_chars_matched==0){
							// Nothing matched, meaning that we need more buffer
							this->current_string.append(&this->_read_buffer[pos],hit-pos);
							//this->_parsing_doing	= do
						}else{
							if(hit-pos>this->wait_for_chars_matched){
								this->current_string.append(&this->_read_buffer[pos],hit-pos-this->wait_for_chars_matched);
							}
							if(this->wait_for_chars_matched==this->wait_for_chars_size){
								// Everything matched and done
								//this->current_string.append(&this->_read_buffer[pos],hit-pos);
								this->_parsing_doing	= doing_done;
								//hit	+= this->wait_for_chars_size;	// Remove matched chars
							}else{
								if(hit<bytes_transferred){
									// Not all expected chars was listed, lets do it again.
									this->wait_for_chars_matched	= 0;
								//}else{
									// We have matched some chars, but run out of buffer. We need to read some more buffer
								}
							}
						}
						pos	= hit;

						// Make sure the string is not too big
						if(this->current_string.size()>this->wait_for_chars_max_limit){
							returncode	= request_bad;
						}
						break;
					case doing_waitfordigit:
						if(this->_read_buffer[pos]>=38 && this->_read_buffer[pos]<=57){
							this->wait_for_digit	= (int)this->_read_buffer[pos] - 38;
							++pos;
						}else{
							returncode	= request_bad;
						}
						this->_parsing_doing	= doing_done;
						break;
				}
			}
			if(this->_parsing_doing==doing_done || this->_parsing_doing==doing_nothing){
				switch(this->_parsing_step){
					case method_start:
						this->request_method.clear();
						this->_parsing_step	= method;
						this->_set_waiting_for_chars(" ",1,7);
						break;
					case method:
						this->request_method.swap(this->current_string);
						this->request_uri.clear();
						this->_parsing_step	= uri;
						this->_set_waiting_for_chars(" HTTP/",6,1024*16);
						break;
					case uri:
						this->request_uri.swap(this->current_string);
						this->_parsing_step	= http_major;
						this->_parsing_doing	= doing_waitfordigit;
						break;
					case http_major:
						this->http_version_major	= this->wait_for_digit;
						this->_parsing_step	= http_dot;
						this->_set_waiting_for_chars(".",1,0);
						break;
					case http_dot:
						this->_parsing_step	= http_minor;
						this->_parsing_doing	= doing_waitfordigit;
						break;
					case http_minor:
						this->http_version_minor	= this->wait_for_digit;
						this->_parsing_step	= new_line;
						this->_set_waiting_for_chars("\r\n",2,0);
						break;
					case new_line:
						// Get line by line and parse them later
						this->_parsing_step	= header;
						this->_set_waiting_for_chars("\r\n",2,4096);	// Max header size 4096
						break;
					case header:
						if(this->current_string.empty()){
							// We got a new line again, request is done
							if(this->request_method=="POST"){
								this->_parsing_step	= request_post;
							}else{
								returncode	= request_done;
							}
						}else{
							// We most likely have a header, lets find a colon
							std::string::size_type colonIndex	= this->current_string.find(":");
							if(colonIndex!=std::string::npos){
								std::string header_key(this->current_string,0,colonIndex);
								this->_variables[header_key]	= boost::algorithm::trim_copy(this->current_string.substr(colonIndex+1));
//std::cout << "-" << header_key << "=" << this->current_connection->_variables[header_key] << "\n";
							}
							this->_set_waiting_for_chars("\r\n",2,4096);	// Max header size 4096
						}
						break;
					case request_post:
						// TODO: Handle POST requests
						break;
					default:
						returncode	= request_done;

				}

				this->current_string.clear();

			}
		}while(returncode==request_unfinished && pos<bytes_transferred);



		boost::system::error_code ec;
		switch(returncode){
			case request_done:
				// Send the request to callback
				{
					this->parse_request_uri();
					bool result(false);

					try{
						result	= this->_server->connection_callback()(this->get_ptr());
					}
					catch(doeplus::webapp::http_exception &e){
						this->_server->handle_error(this->get_ptr(),e);
					}
					catch(...){
						doeplus::webapp::http_exception e(500,"Internal server error");
						this->_server->handle_error(this->get_ptr(),e);
					}
					if(!result) {
						doeplus::webapp::http_exception e(404,"No found");
						this->_server->handle_error(this->get_ptr(),e);
					}
				}
				break;
			case request_unfinished:
				// Continue to read request
				this->read_request();
				break;
			default:
				// TODO: Maybe we should send a 400 error
				this->_server->error_callback()(this->get_ptr(),doeplus::webapp::server::HTTP_error_parsing_request);

				// There was an error, lets drop the connection
				// First shut down, then close the connection
				//this->_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec);
				//this->_socket.close(ec);
				// It's pretty uninterresting to know if this fails :)
		}
	}

	void read_request(){
		// do a async reading on the socket
		this->_socket.async_read_some(
			boost::asio::buffer(this->_read_buffer,HTTP_READ_BUFFER_SIZE),
			boost::bind(&connection::handle_read,this->get_ptr(),boost::asio::placeholders::error,boost::asio::placeholders::bytes_transferred)
		);
	}

public:
	void start(doeplus::webapp::server* set_server){
		this->_server	= set_server;
		this->read_request();
	}
	void init(doeplus::webapp::server* server){
		this->_response_headers	= server->default_headers;
	}
public:
	void handle_write(const boost::system::error_code& error){
		this->response_buffer_sending.pop_front();
		if( (this->response_buffer_sending.empty() && this->close_response) || error){
			// Remove connection
			this->_server->_remove_connection(this->get_ptr());
		}
		//std::cout << "\n";
	}
private:
	void send_buffer(buffer_ptr buf){
		this->response_buffer_sending.push_back(buf);
		boost::asio::async_write(
				this->_socket,
				boost::asio::buffer(&buf->_buffer,buf->_size),
				boost::bind(&doeplus::webapp::connection::handle_write,this->get_ptr(),boost::asio::placeholders::error)
		);
	}

public:
	void send_response(const std::string& response,bool last_response){
		if(!this->response_headers_send){
			this->response_headers_send	= true;
			// Generate headers
			boost::format response_head("HTTP/1.0 %1% OK\r\n");
			response_head % this->response_code;
			std::string header_string(response_head.str());

			doeplus::webapp::string_map& headers 	= this->_response_headers;
			for(doeplus::webapp::string_map::iterator it(headers.begin());it!=headers.end();++it){
				header_string.append(it->first);
				header_string.append(": ");
				header_string.append(it->second);
				header_string.append("\r\n");
			}
			header_string.append("\r\n");
			this->send_response(header_string,false);
		}

		// Add to output buffer
		std::size_t pos(0);
		while(pos<response.size()){
			std::size_t buffer_size_left(HTTP_RESPONSE_BUFFER_SIZE-this->current_response_buffer->_size);
			if(buffer_size_left==0){
				// Send old buffer, then create new buffer
				this->send_buffer(this->current_response_buffer);
				this->current_response_buffer.reset(new buffer());
				buffer_size_left	= HTTP_RESPONSE_BUFFER_SIZE;
			}
			std::size_t copy_buffer_size	= buffer_size_left < response.size()-pos ? buffer_size_left:response.size()-pos;
			memcpy(&this->current_response_buffer->_buffer[this->current_response_buffer->_size],response.c_str()+pos,copy_buffer_size);
			this->current_response_buffer->_size	+= copy_buffer_size;
			pos	+= copy_buffer_size;
		}
		if(last_response){
			if(this->current_response_buffer->_size){
				this->send_buffer(this->current_response_buffer);
				this->current_response_buffer.reset();
			}
			this->close_response	= true;
		}
	}



};


} } } // doeplus::webapp::protocol

#endif /* HTTP_HPP_ */
